package cn.gyn.gynrpc.core.registry.gyn;

import cn.gyn.gynrpc.core.api.RegistryCenter;
import cn.gyn.gynrpc.core.consumer.HttpInvoker;
import cn.gyn.gynrpc.core.meta.InstanceMeta;
import cn.gyn.gynrpc.core.meta.ServiceMeta;
import cn.gyn.gynrpc.core.registry.ChangedListener;
import cn.gyn.gynrpc.core.registry.Events;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * implementation for gyn registry center
 * register\register给服务提供者用，findAll给consumer端拿到所有实例信息用，订阅给consumer刷新获取最新实例信息用
 *
 * @author gyn
 * @date 2024年9月14日
 */
@Slf4j
public class GynRegistryCenter implements RegistryCenter {

    private static final String REG_PATH = "/reg";
    private static final String UNREG_PATH = "/unreg";
    private static final String FINDALL_PATH = "/findAll";
    private static final String VERSION_PATH = "/version";
    private static final String RENEWS_PATH = "/renews";

    GynHealthChecker healChecker = new GynHealthChecker();

    @Value("${gynregistry.servers}")
    private String servers;

    //服务-版本 ，用于存本地版本用
    Map<String, Long> VERSIONS = new HashMap<>();
    //一个实例会有多个服务接口方法
    MultiValueMap<InstanceMeta, ServiceMeta> RENEWS = new LinkedMultiValueMap<>();

    @Override
    public void start() {
        log.info("===========>>>> [gynregistry] : start with server : {}", servers);
        healChecker.start();
        providerCheck();
    }

    @Override
    public void stop() {
        log.info(" =====> [gynregistry] : stop with server : {}", servers);
        healChecker.stop();
    }

    @Override
    public void register(ServiceMeta service, InstanceMeta instance) {
        log.info(" =====> [gynregistry] : register instance {} for {}", instance, service);
        HttpInvoker.httpPost(JSON.toJSONString(instance), regPath(service), InstanceMeta.class);
        log.info(" =====> [gynregistry] : registered instance {}", instance);
        RENEWS.add(instance, service);

        /**
         * 相当于：
         * ### 注册一个实例
         * POST http://localhost:8486/reg?service=io.github.annyylxyms.gynrpc.UserService
         * Content-Type: application/json
         *
         * {
         *   "context": "gynrpc",
         *   "host": "127.0.0.1",
         *   "port": 8083,
         *   "scheme": "http",
         *   "status": false,
         *   "parameters": {
         *     "env": "dev",
         *     "tag": "RED"
         *   }
         * }
         */
    }


    @Override
    public void unRegister(ServiceMeta service, InstanceMeta instance) {
        log.info(" =====> [gynregistry] : unregister instance {} for {}", instance, service);
        HttpInvoker.httpPost(JSON.toJSONString(instance), unregPath(service), InstanceMeta.class);
        log.info(" =====> [gynregistry] : unregistered instance {}", instance);
        RENEWS.remove(instance, service);
    }


    @Override
    public List<InstanceMeta> fetchAll(ServiceMeta service) {
        log.info(" =====> [gynregistry] : fetchAll instance for {}", service);
        //返回类型：List<InstanceMeta>用typreference包一下！！！学到了！
        List<InstanceMeta> instances = HttpInvoker.httpGet(findAllPath(service), new TypeReference<List<InstanceMeta>>() {
        });
        log.info(" =====> [gynregistry] : fetchAll = {}", instances);
        return instances;
    }


    public void providerCheck() {
        //provider 每5s调用renew探活
        healChecker.providerCheck(() -> {
            RENEWS.keySet().forEach(instance -> {
                Long timestamp = HttpInvoker.httpPost(JSON.toJSONString(instance), renewsPath(RENEWS.get(instance)), Long.class);
                log.info("===========>>>> [gynregistry] : renew instance {} at {}", instance, timestamp);
            });
        });
    }
    @Override
    public void subscribe(ServiceMeta service, ChangedListener listenr) {
        //用定时任务，拿服务版本和本地版本做对比
        //延迟1s执行，每5秒执行一次
        healChecker.consumerCheck(() -> {
            //获取当前版本，没有给一个默认值-1
            Long version = VERSIONS.getOrDefault(service.toPath(), -1L);
            //取注册中心里的版本
            Long newVersion = HttpInvoker.httpGet(versionPath(service), Long.class);
            log.info(" =====> [gynregistry] : version for {} is {}, new version is {}", service, version, newVersion);
            if (newVersion > version) {
                //版本有更新，fetchAll
                List<InstanceMeta> instances = fetchAll(service);
                //新的服务节点更新provider
                listenr.fire(new Events(instances));
                //细节：下面一行放最后，都完成了才刷新本地版本号，如果中途不成功，下次还是可以重刷。
                VERSIONS.put(service.toPath(), newVersion);
            }
        });
    }


    private String regPath(ServiceMeta service) {
        return path(REG_PATH, service);
    }

    private String unregPath(ServiceMeta service) {
        return path(UNREG_PATH, service);
    }

    private String findAllPath(ServiceMeta service) {
        return path(FINDALL_PATH, service);
    }

    private String versionPath(ServiceMeta service) {
        return path(VERSION_PATH, service);
    }

    private String renewsPath(List<ServiceMeta> serviceList) {
        return path(RENEWS_PATH, serviceList);
    }

    private String path(String context, ServiceMeta service) {
        return servers + context + "?service=" + service.toPath();
    }

    private String path(String context, List<ServiceMeta> serviceList) {
        StringBuffer sb = new StringBuffer();
        for (ServiceMeta service : serviceList) {
            sb.append(service.toPath()).append(",");
        }
        String services = sb.toString();
        if (services.endsWith(",")) services = services.substring(0, services.length() - 1);
        log.info("===========>>>> [gynregistry] : renew instance for {}", services);
        return servers + context + "?services=" + services;
    }
}
